//-----------------------------------------------------------------------------------------------
// Author: Andre Van Der Merwe <dart@pobox.com>
// Project: .net Compound File (http://dotnetpcf.sourceforge.net)
// 
// Copyright (c) Andre Van Der Merwe 2000-2001
// Licence: BSD - see the licence.txt file or visit the project homepage
//-----------------------------------------------------------------------------------------------


namespace dotnetpcf.CompoundFile
{
   using System;
   using System.Diagnostics;
   using System.Collections;
   using System.Data;
   using System.IO;

   public enum DirFlags : uint
   {
      dfNone = 0x00, //must be used in an equality check only
      dfUser = 0x01,
      dfSystem = 0x02
   }

   /// <summary>
   /// Directories work exactly like their file system counterparts. However they can have file names
   /// of any length and contain any characters.
   /// </summary>
   /// <remarks>
   /// 
   /// </remarks>
   internal class DirTable
   {
      internal class DirTableEntry
      {
         public const uint SIZE = 20;

         //-------------
         // Header info
         //-------------
         public uint m_iID = SysInfo.UNDEFINED;
         public uint m_iNameID = SysInfo.UNDEFINED;        //CFNames ID
         public uint m_iParentID = SysInfo.UNDEFINED;      //Dir table ID
         public uint m_iPropID = SysInfo.UNDEFINED;        //File table ID
         public uint m_iFlags = 0;

         //-------------
         //Runtime info
         //-------------
         private DirTable m_DirTable = null;


         public DirTableEntry(  DirTable tbl  )
         {
            m_DirTable = tbl;  
         }

         public void LoadFromStream(  Stream strm  )
         {
            Debug.Assert(  null != strm  );
            if(  null == strm  )
               throw(  new CompoundFileException(  CompoundFileResult.cfrStreamIsNull  )  );

            BinaryReader rdr = new BinaryReader(  strm  );

            m_iID = rdr.ReadUInt32();
            m_iNameID = rdr.ReadUInt32();
            m_iParentID = rdr.ReadUInt32();
            m_iPropID = rdr.ReadUInt32();
            m_iFlags = rdr.ReadUInt32();
         }

         public void SaveToStream(  Stream strm  )
         {
            Debug.Assert(  null != strm  );
            if(  null == strm  )
               throw(  new CompoundFileException(  CompoundFileResult.cfrStreamIsNull  )  );

            BinaryWriter wrtr = new BinaryWriter(  strm  );

            wrtr.Write(  m_iID  );
            wrtr.Write(  m_iNameID  );
            wrtr.Write(  m_iParentID  );
            wrtr.Write(  m_iPropID  );
            wrtr.Write(  m_iFlags  );
         }

         public string Name
         {
            get{  return m_DirTable.m_cfParent.Names.GetNameByID( m_iNameID );  }

            set
            {
               DirTableEntry dentParent = (DirTableEntry)m_DirTable.m_Entries[ m_iParentID ];

               if(   !m_DirTable.CanSetDirName(  value,  dentParent,  this  )   )
                  throw(  new CompoundFileException(  CompoundFileResult.cfrInvalidDirName  )  );

                  //New name?
               if(  SysInfo.UNDEFINED == m_iNameID  )
               {
                     //Dont set an empty name
                  if(  ("" == value) || (null == value)  )
                     return;
                     
                  m_iNameID = m_DirTable.m_cfParent.Names.GetNextFreeNameID();
                  m_DirTable.m_cfParent.Names.SetName(  m_iNameID,  value  );
               }
               else
               {
                  if(  ("" != value) && (null != value)  )
                     m_DirTable.m_cfParent.Names.SetName(  m_iNameID,  value  );
                  else
                  {
                        //Delete old name
                     m_DirTable.m_cfParent.Names.Remove(  m_iNameID  );
                     m_iNameID = SysInfo.UNDEFINED;
                  }
               }
            }
         }
      }

      //-----------------
      private CompoundFileSys m_cfParent;
      private BlockHeader m_Header = new BlockHeader();
      private Hashtable m_Entries = new Hashtable();

      public DirTable(  CompoundFileSys cf  )
      {
         m_cfParent = cf;
         m_Header.m_iMagic = SysInfo.MAGIC_DIRTBL_HEADER;
      }

      public void SaveToStream(  FSFilterStream strm  )
      {
         Debug.Assert(  null != strm  );
         if(  null == strm  )
            throw(  new CompoundFileException(  CompoundFileResult.cfrStreamIsNull  )  );

         long iStartPos = strm.Position;
         m_Header.m_iTotalItems = (uint)m_Entries.Count;
         m_Header.SaveToStream(  strm  );

         IDictionaryEnumerator elems = m_Entries.GetEnumerator();
   
         while(  elems.MoveNext()  )
            ((DirTableEntry)elems.Value).SaveToStream(  strm  );
            
      }
      
      public void LoadFromStream(  FSFilterStream strm  )
      {
         Debug.Assert(  null != strm  );
         if(  null == strm  )
            throw(  new CompoundFileException(  CompoundFileResult.cfrStreamIsNull  )  );

         long iStartPos = strm.Position;
         m_Header.LoadFromStream(  strm  );

         if(  SysInfo.MAGIC_DIRTBL_HEADER != m_Header.m_iMagic  )
            throw(  new CompoundFileException(  CompoundFileResult.cfrInvalidDirTableMagicNumbers  )  );


         for(  int i = 0;  i < m_Header.m_iTotalItems;  ++i  )
         {
            DirTableEntry dent = new DirTableEntry(  this  );
            dent.LoadFromStream(  strm  );
            
            Debug.Assert(  SysInfo.UNDEFINED != dent.m_iID  );
            m_Entries.Add(  (uint)(dent.m_iID),  dent  );            
         }
         
         //TODO Check that num loaded = header.numInBlock
         Debug.Assert(  m_Entries.Count == m_Header.m_iTotalItems,  "Incorrect number of file entries loaded"  );
      }


      internal uint GetNextFreeDirID()
      {
            //Find a new ID
         uint iFirstFreeDir = 0;
         while(  m_Entries.ContainsKey(  ++iFirstFreeDir  )   )
         {}

         return iFirstFreeDir;
      }

      public bool CanSetDirName(  string sDirName,  DirTableEntry dirParent,  DirTableEntry dirCurrent  )
      {
            //Check that no directories with the name already exist
         if(  ("" != sDirName) && (null != sDirName)  )
         {
            IDictionaryEnumerator elems = m_Entries.GetEnumerator();
            while(  elems.MoveNext()  )
            {
                  //Ignore the 'current item' item.
               if(  (null != dirCurrent) && (((DirTableEntry)elems.Value).m_iID == dirCurrent.m_iID)  )
                  continue;

               if(  null != dirParent  )
               {
                     //If there are any other child dirs under the parent dir with the same name
                  if(  (dirParent.m_iID == ((DirTableEntry)elems.Value).m_iParentID) && (sDirName == ((DirTableEntry)elems.Value).Name)  )
                     return false;
               }
               else
               {
                     //If there are any other dirs off the root with the same name
                  if(  (SysInfo.UNDEFINED == ((DirTableEntry)elems.Value).m_iParentID) && (sDirName == ((DirTableEntry)elems.Value).Name)  )
                     return false;
               }
            }
         }

         return true;
      }

      public DirTableEntry CreateDirectory(  string sDirName,  DirTableEntry dirParent  )
      {
         return CreateDirectory(  sDirName,  dirParent,  DirFlags.dfUser  );         
      }

      public DirTableEntry CreateDirectory(  string sDirName,  DirTableEntry dirParent,  DirFlags flags  )
      {
            //Check that this is a valid name for this dir
         if(   !CanSetDirName(  sDirName,  dirParent,  null  )   )
            return null;

            //Find a new ID
         uint iFirstFreeDir = GetNextFreeDirID();

         DirTableEntry dir = new DirTableEntry(  this  );

         try
         {
            dir.m_iID = iFirstFreeDir;
            dir.m_iFlags = (uint)flags;
            m_Entries[ iFirstFreeDir ] = dir;

            if(  null != dirParent  )
            {
               dir.m_iParentID = dirParent.m_iID;
               Debug.Assert(  SysInfo.UNDEFINED != dirParent.m_iID  );
            }
   
               //Set the dir name
            dir.Name = sDirName;
         }
         catch(  CompoundFileException  )
         {
            m_Entries.Remove(  dir.m_iID  );
            m_cfParent.Names.Remove(  dir.m_iNameID  );
            throw;
         }

         return dir;
      }

      public bool DeleteDirEntry(  uint iID  )
      {
         if(   (SysInfo.UNDEFINED == iID) || (!m_Entries.ContainsKey(  iID  ))   )
            return false;

         IDictionaryEnumerator elems = m_Entries.GetEnumerator();

            //Cant delete if this dir has children
         while(  elems.MoveNext()  )
            if(   iID == ((DirTable.DirTableEntry)elems.Value).m_iParentID   )
               return false;

            //Check that no files belong to this dir
         elems = m_cfParent.FileTable.GetEntryEnumerator();
         while(  elems.MoveNext()  )
            if(   iID == ((FileTable.FileTableEntry)elems.Value).m_iDirID   )
               return false;

         DirTableEntry dent = (DirTableEntry)m_Entries[ iID ];
         dent.Name = null;
         m_cfParent.FileTable.DeleteFileEntry(  dent.m_iPropID  );

         m_Entries.Remove(  iID  );
         return true;
      }

      public DirTable.DirTableEntry GetDirTableEntryByID(  uint iID  )
      {
         return (DirTable.DirTableEntry)m_Entries[ iID ];
      }

         /// <summary>
         /// Number of file table entrys
         /// </summary>
      public int EntryCount
      {
         get{ return m_Entries.Count; }
      }


      public IDictionaryEnumerator GetEntryEnumerator()
      {
         return m_Entries.GetEnumerator();
      }
   }
}
